{
  "cells": [
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "view-in-github"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/hsgw/keyboard-made-by-python/blob/main/notebook/en/pcb.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "> Project page is [Keyboard as a Python Code](https://hackaday.io/project/188907-keyboard-as-a-python-code)\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "g6rr65y7TUEE"
      },
      "source": [
        "# Design PCB\n",
        "\n",
        "<img src=\"../imgs/kbd_python_preview_top.png\" height=\"300px\">\n",
        "<img src=\"../imgs/kbd_python_preview_bot.png\" height=\"300px\">\n",
        "\n",
        "The goal of this notebook is to design the circuit for a keyboard and to get `Gerber files` for the production.\n",
        "To make a pcb in the normal way, first draw a `circuit diagram` in the `circuit diagram editor` and output it as a `netlist` with the components to be used and their wiring information, and then physically place and wire the components on the `PCB` using the `PCB editor`."
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "RwgtrhL6DhoQ"
      },
      "source": [
        "## NOTE TO READ ARTICLE.\n",
        "This notebook contains cells that will not work properly if run twice.\n",
        "\n",
        "To prevent this, you should run `Runtime>Execute All Cells` from the menu above before starting to read the article. It will take some time for the execution to complete."
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "Topn5GuhW6Z2"
      },
      "source": [
        "## Download resources\n",
        "Run the following cell to clone the repository with kicad libraries and other resources."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "hEThVfBsW8RE",
        "outputId": "b4211474-89e8-4cf3-9779-fdbd6e895db2"
      },
      "outputs": [],
      "source": [
        "!git clone https://github.com/hsgw/keyboard-made-by-python/"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "Ancu-CO6W_Y4"
      },
      "source": [
        "# Design a netlist (schematic) with skidl\n",
        "If you design with Python, you can define components and wiring information by code without drawing a schematic, and output a netlist directly.\n",
        "\n",
        "[skidl](https://github.com/devbisme/skidl) uses the kicad component library as well as its own formats. The netlist is also compatible with the kicad and can be directly imported into pcbnew to make the pcb.\n",
        "\n",
        "- skidl  https://github.com/devbisme/skidl\n",
        "- skidl Document  https://devbisme.github.io/skidl/"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "-cW5lSiPYuUS"
      },
      "source": [
        "## Install and import skidl\n",
        "Install and import [skidl](https://github.com/devbisme/skidl)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Oy3vcMzUWq9g",
        "outputId": "aa8168c0-4184-4e8b-cd14-815035fc8882"
      },
      "outputs": [],
      "source": [
        "# Install will take some time\n",
        "!pip install skidl"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "KMF3_yPHTF0L",
        "outputId": "e51fa261-a270-4d10-c6a9-b27fae81e2b9"
      },
      "outputs": [],
      "source": [
        "# WARNING because the default path for kicad is not set in the environment variable\n",
        "from skidl import *"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "LTeD6nJwogVT"
      },
      "source": [
        "## Declare constants\n",
        "Declare values that appear repeatedly as constants. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6jwqmkIjo5MH"
      },
      "outputs": [],
      "source": [
        "KEY_COUNT = 10\n",
        "COL_COUNT = 4\n",
        "ROW_COUNT = 3\n",
        "MATRIX_MAP = [\n",
        "        (0,1),(0,2),(0,3),  \n",
        "        (1,1),(1,2),(1,3),\n",
        "  (2,0),(2,1),(2,2),(2,3)\n",
        "]"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "QWaPdvrNmmzk"
      },
      "source": [
        "## Loading kicad library and define parts\n",
        "Add the path to the kicad library to be used.\n",
        "\n",
        "If kicad is installed on your computer, the path to the default library has been added. In this environment, pass the path to the required libraries only.   \n",
        "They are in `keyboard-made-by-python/hardware/kicad_libs` that has just been cloned from github."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "EjCwQBClnF5q",
        "outputId": "0c0ad665-d336-4d2a-af5e-55f8886532ac"
      },
      "outputs": [],
      "source": [
        "# resistor library paths\n",
        "# require both symbol and footprint\n",
        "lib_search_paths[KICAD].append(\"keyboard-made-by-python/hardware/kicad_libs\")\n",
        "footprint_search_paths[KICAD].append(\"keyboard-made-by-python/hardware/kicad_libs/kicad.pretty\")\n",
        "\n",
        "# If you use the same part more than one, define it as template with a symbol and footprint\n",
        "diode = Part(\n",
        "  \"kicad_symbols\", \"D_Small_ALT\", TEMPLATE, footprint=\"kicad:D_SOD123_hand\"\n",
        ")\n",
        "switch = Part(\n",
        "  \"kicad_symbols\",\n",
        "  \"SW_Push\",\n",
        "  TEMPLATE,\n",
        "  footprint=\"kicad:SW_Cherry_MX_1.00u_PCB\",\n",
        ")\n",
        "\n",
        "# define diodes and switches as array\n",
        "diodes = diode(KEY_COUNT)\n",
        "switches = switch(KEY_COUNT)\n",
        "\n",
        "xiao = Part(\"kicad_symbols\", \"xiao_rp2040\", footprint=\"kicad:xiao_rp2040\")\n",
        "oled = Part(\"kicad_symbols\", \"oled_i2c\", footprint=\"kicad:oled_i2c\")\n",
        "\n",
        "# You can see the Pin information of the symbol when you print\n",
        "print(diode, switch, xiao, oled)"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "oG5xuNjqq6qg"
      },
      "source": [
        "## Connecting Pins and Net\n",
        "Define a circuit by entering connections between the pins of the prepared components.\n",
        "\n",
        "If possible, you should name the wiring itself with `Net`. By connecting the `Pins` of the components based on the `Net`, you can make the code easier to read by organizing the connections to the same place (power supply, switch matrix, etc.). It also serves as a good guide when importing a netlist into kicad, as it will be displayed there.\n",
        "\n",
        "This design will also read the `pin numbers` from `Net` when wiring the PCB."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "PI9UK581tf5H",
        "outputId": "3cb4724e-8401-4655-a2e2-66fb5cb5368f"
      },
      "outputs": [],
      "source": [
        "# Viewing pin information is helpfull when wiring\n",
        "print(xiao)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Ufo-bwPNtWzt",
        "outputId": "833c4292-eef8-46e5-c648-298eb0fe0c53"
      },
      "outputs": [],
      "source": [
        "# Make an array containing the netlist of ROW and COL of the switch matrix\n",
        "netRows = [Net(f\"ROW{i}\") for i in range(ROW_COUNT)]\n",
        "netCols = [Net(f\"COL{i}\") for i in range(COL_COUNT)]\n",
        "\n",
        "\n",
        "# Connect ROW's Net -> switch's Pin 1 switch's Pin 2 -> diode's cathode, diode's anode -> COL's Net\n",
        "# Net and Pin are connected by `&`\n",
        "# Pin of a component can be accessed by part[\"pin name\"]\n",
        "# Pin of a part can be accessed by part[\"pin name\"] \n",
        "# Two subscripts for in and out\n",
        "# Example: Net or Pin connected to sw[\"1\"] & sw[\"1 2\"] & Net or Pin connected to sw[\"2\"]\n",
        "for sw, d, mapping in zip(switches, diodes, MATRIX_MAP):\n",
        "  netRows[mapping[0]] & sw[\"1 2\"] & d[\"K A\"] & netCols[mapping[1]]\n",
        "\n",
        "# Connect the switch matrix to xiao\n",
        "# Add Pin to Net by `+`\n",
        "netCols[0] += xiao[8]\n",
        "netCols[1] += xiao[3]\n",
        "netCols[2] += xiao[4]\n",
        "netCols[3] += xiao[5]\n",
        "\n",
        "netRows[0] += xiao[1]\n",
        "netRows[1] += xiao[6]\n",
        "netRows[2] += xiao[7]\n",
        "\n",
        "# Connect oled and xiao\n",
        "# You can connect by declaring Net directly like Net(\"3.3V\")\n",
        "Net(\"3.3V\") & oled[\"Vcc\"] & xiao[\"3.3V\"]\n",
        "Net(\"GND\") & oled[\"GND\"] & xiao[\"GND\"]\n",
        "Net(\"SDA\") & oled[\"SDA\"] & xiao[9]\n",
        "Net(\"SCL\") & oled[\"SCL\"] & xiao[11]\n",
        "\n",
        "# printing Net shows connected Pins\n",
        "print(netRows[0])"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "ygS8U7MywJ8k"
      },
      "source": [
        "## Run ERC and export netlist\n",
        "Run ERC (Electrical Rule Check, schematic rule check) and export the netlist.\n",
        "Now the schematic/netlist is complete!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 330
        },
        "id": "7UCaNTPgwJVV",
        "outputId": "209f3e2a-ff1e-4806-d9b4-13bd61d0f927"
      },
      "outputs": [],
      "source": [
        "# You may get an error if you run other cells more than once\n",
        "# In that case, restart and run all cells again\n",
        "# You will get a warning about un-wired cells, but no problem\n",
        "ERC()\n",
        "generate_netlist(file_=\"keyboard.net\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "5URJ6fd7xzse"
      },
      "source": [
        "The generated netlist can be imported by kicad pcbnew to create pcb as-is.\n",
        "\n",
        "![imported by kicad pcbnew](https://github.com/hsgw/keyboard-made-by-python/blob/main/notebook/imgs/kicad_pcbnew.png?raw=1)"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "rtU9DM-AD2hu"
      },
      "source": [
        "# Designing a PCB with pcbflow\n",
        "Using [pcbflow](https://github.com/michaelgale/pcbflow), read the footprint and pin connections designed in skidl and physically place the components and wiring on the PCB. As needed, you can preview the image files and finally export a complete set of Gerber files for manufacturing.\n",
        "\n",
        "The routing is based on the traditional [Turtle graphics](https://docs.python.org/ja/3/library/turtle.html)-like notations.\n",
        "\n",
        "pcbflow is under development(?). and had some problems and unexpected behavior, so I have fixed and updated it. [folk](https://github.com/hsgw/pcbflow/tree/fix_kicad)\n",
        "\n",
        "- pcbflow https://github.com/michaelgale/pcbflow\n",
        "- folked pcbflow https://github.com/hsgw/pcbflow/tree/fix_kicad"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "mXU5TZOeFBGI"
      },
      "source": [
        "## Install pcbflow\n",
        "Execute the following cells to install.\n",
        "Import and verify that there are no errors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "D4V_SArjWQXs",
        "outputId": "1d0507f1-bea5-4027-fa2c-08d44f90cbb0"
      },
      "outputs": [],
      "source": [
        "# Takes a few minutes to install\n",
        "!pip install git+https://github.com/hsgw/pcbflow/@fix_kicad"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zZH-ygq3FlJu"
      },
      "outputs": [],
      "source": [
        "from pcbflow import *"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "qkWfPZF2I37R"
      },
      "source": [
        "## Declare constants\n",
        "As in the netlist, frequently used values are declared as constants.   \n",
        "I also declare a function to convert the Y-axis of the coordinates because it was inverted and felt uncomfortable."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "a031kS7yJEtJ"
      },
      "outputs": [],
      "source": [
        "BOARD_WIDTH = 76.0\n",
        "BOARD_HEIGHT = 57.0\n",
        "\n",
        "KEY_PITCH = 19.0\n",
        "\n",
        "SCREW_HOLE = 2.2\n",
        "\n",
        "LAYER_TOP = \"GTL\"\n",
        "LAYER_BOTTOM = \"GBL\"\n",
        "\n",
        "# Invert Y-axis\n",
        "def pos(x, y):\n",
        "  return (x, BOARD_HEIGHT - y)"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "K2o9ziq3N2Bb"
      },
      "source": [
        "## Declare variables for the PCB and circuit\n",
        "Declare variables to access the PCB and the circuit.\n",
        "The PCB is from pcbflow and the circuit is from skidl.\n",
        "\n",
        "When declaring the `board`, specify the size of the outline and add an outline line."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8bzraF-yOaIH"
      },
      "outputs": [],
      "source": [
        "# from skidl\n",
        "circuit = builtins.default_circuit\n",
        "# for pcbflow\n",
        "board = Board((BOARD_WIDTH, BOARD_HEIGHT))\n",
        "board.add_outline()"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "9nWBUCJJNdgE"
      },
      "source": [
        "## Configure design rules\n",
        "Configure the design rules for the PCB, such as drill size, wire width, and so on.\n",
        "\n",
        "Use these settings as the basis for routing the PCB."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "PdPPua_3Nuoi"
      },
      "outputs": [],
      "source": [
        "board.drc.trace_width = 0.5\n",
        "board.drc.via_drill = 0.6\n",
        "board.drc.via_annular_ring = 0.4\n",
        "board.drc.clearance = 0.4"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "NFHOh_4xaSMc"
      },
      "source": [
        "## Add drill holes\n",
        "Make a hole to mount the PCB."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1AY3bcKpaa6M"
      },
      "outputs": [],
      "source": [
        "board.add_hole(pos(KEY_PITCH * 2, KEY_PITCH), SCREW_HOLE)\n",
        "board.add_hole(pos(KEY_PITCH * 3, KEY_PITCH), SCREW_HOLE)\n",
        "board.add_hole(pos(KEY_PITCH * 3, KEY_PITCH * 2), SCREW_HOLE)\n",
        "board.add_hole(pos(KEY_PITCH, KEY_PITCH * 2), SCREW_HOLE)"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "gxtMIg5IcLKb"
      },
      "source": [
        "### Generate image and preview\n",
        "Now let's generate image for preview"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "22HqPC8IcsFh",
        "outputId": "3c5cb1e1-9994-4dbc-9208-43b08752bbcb"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "# Export the current board as a png file\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "# Show on output\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "t2mUZjAfY31k"
      },
      "source": [
        "## Add position info to a part\n",
        "Add position, rotation, and mounting surface info on the PCB to each of the skidl parts.\n",
        "\n",
        "In this article, the position is specified immediately, but I repeatedly placed the parts on the PCB and previewed them to determine the position."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "R4cUuHhZZPm_"
      },
      "outputs": [],
      "source": [
        "# sw, d, xiao, oled are from skidl section\n",
        "\n",
        "# Switches and diodes are positioned the same for every block\n",
        "for sw, d, mapping in zip(switches, diodes, MATRIX_MAP):\n",
        "  sw.pos = pos(\n",
        "    mapping[1] * KEY_PITCH + KEY_PITCH / 2,\n",
        "    mapping[0] * KEY_PITCH + KEY_PITCH / 2,\n",
        "  )\n",
        "  sw.side = \"top\"\n",
        "  sw.rotate = 0\n",
        "  d.pos = (sw.pos[0] + 6, sw.pos[1] + 5)\n",
        "  d.side = \"bottom\"\n",
        "  d.rotate = 0\n",
        "\n",
        "xiao.pos = pos(11.25, 11.5)\n",
        "xiao.side = \"bottom\"\n",
        "xiao.rotate = 0\n",
        "\n",
        "oled.pos = pos(9.5, 36)\n",
        "oled.side = \"top\"\n",
        "oled.rotate = 270"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "jQy4fcGbfAfd"
      },
      "source": [
        "## Place the components on the PCB\n",
        "Based on the location information added, place the parts in skidl while converting them to the PCB in pcbflow."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "MBdn1GA4e_ea"
      },
      "outputs": [],
      "source": [
        "for part in circuit.parts:\n",
        "  try:\n",
        "    # convert skidl parts to pcbflow\n",
        "    SkiPart(board.DC(part.pos).right(part.rotate), part, side=part.side)\n",
        "  except AttributeError:\n",
        "    print(f\"{part.ref} has no pos or side\")\n",
        "    continue"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "376guVBxgOOm"
      },
      "source": [
        "### Preview"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "pMku38a9gIMl",
        "outputId": "e09a38c7-d36a-4ed8-88b7-e3031accccbb"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "# Export the current board as a png file\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "# Show\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "HIvuWJ6_sxcM"
      },
      "source": [
        "## Wiring: xiao to switch (ROW)\n",
        "Route the wires from xiao to ROW.   \n",
        "Use Turtle syntax to draw wires from xiao and connect them to `Pad` on the switch.\n",
        "\n",
        "The `Pin` in skidl and `pads` in pcbflow refer to the pins of the component."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "QYuy9Np7sx9M",
        "outputId": "ed24ff82-e77b-46f8-fba6-2d8cdfdee9fe"
      },
      "outputs": [],
      "source": [
        "xiaoRef = \"U1\"\n",
        "\n",
        "# Function to return Pad number by name from skidl's Net\n",
        "# skidl's Pin number starts from 1, pcbflow's Pad number starts from 0\n",
        "def get_pin_number_from_net(netLabel, ref):\n",
        "    net = Net.get(netLabel)\n",
        "    return list((int(x.num) - 1 for x in net.pins if x.ref == ref))[0]\n",
        "\n",
        "# Function to return the connected xiao pad by name from skidl's Net\n",
        "# Fixes a wrong pad number due to the kicad library and returns it.\n",
        "# last newpath() fixes strange value in initial value\n",
        "def xiao_pads(net: str):\n",
        "  return (\n",
        "    board.get_part(xiaoRef)\n",
        "    .pads[get_pin_number_from_net(net, xiaoRef) + 14]\n",
        "    .newpath()\n",
        "  )\n",
        "\n",
        "# Connect the pins of the xiao connected to Net to the pins of the switch\n",
        "# Pad of xiao on ROWn -> specify layer -> route using Turtle syntax -> align axis up to pad of SW\n",
        "xiao_pads(\"ROW0\").set_layer(LAYER_TOP).w(\"r 45\").align_meet(\n",
        "  board.get_part(\"SW1\").pads[0], \"x\"\n",
        ")\n",
        "xiao_pads(\"ROW1\").set_layer(LAYER_TOP).w(\"r 45\").align_meet(\n",
        "  board.get_part(\"SW4\").pads[0], \"y\"\n",
        ")\n",
        "xiao_pads(\"ROW2\").set_layer(LAYER_TOP).w(\"l 180 f 12 r 45\").align_meet(\n",
        "  board.get_part(\"SW8\").pads[0], \"y\"\n",
        ")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "fSKc2qDnu_c3"
      },
      "source": [
        "### Preview"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 965
        },
        "id": "bA690BhQu_c8",
        "outputId": "dae75eb2-5ee9-47a2-8a80-598494b4f07e"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "7g0j_Biziy3k"
      },
      "source": [
        "## Wiring: diode - switch, switch - switch (ROW), diode - diode (COL)\n",
        "If the positions of the connected pins are the same, they can be connected in the same code.\n",
        "\n",
        "In routing COL, a via is created on the way. Store the information of the via and connect it to xiao later."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "tfu1rTPnjlSW"
      },
      "outputs": [],
      "source": [
        "# Switches and diodes are arranged in the same way as a block so they can be routed with the same code\n",
        "# Starts from 1 because of accessing by REF No.\n",
        "for i in range(1, KEY_COUNT + 1):\n",
        "  sw = board.get_part(f\"SW{i}\")\n",
        "  d = board.get_part(f\"D{i}\")\n",
        "  # Specify pad to start routing → Specify layer → Routing in Turtle syntax → Align pad and axis and connect.\n",
        "  d.pads[0].set_layer(LAYER_BOTTOM).w(\"f 1 r 45\").align_meet(sw.pads[1], \"x\")\n",
        "\n",
        "# The pins of the switches on the ROW of the switch matrix are aligned in a straight line, so they can be wired in the same way.\n",
        "# Note that the number of switches differs depending on the ROW.\n",
        "for i in range(3):\n",
        "  # Make an array of Ref No. of connected to SW from Net information of ROW.\n",
        "  refs = list(x.ref for x in netRows[i].pins if x.ref.startswith(\"SW\"))\n",
        "  for j in range(len(refs) - 1):\n",
        "    # Specify pad to start routing → Create new path → Specify layer → Routing in Turtle syntax → Route in a straight line to the next pad\n",
        "    board.get_part(refs[j]).pads[0].newpath().set_layer(LAYER_TOP).w(\n",
        "      \"r 90 f 2 l 45 f 1 r 45 f 2.5 r 45 f1\"\n",
        "    ).meet(board.get_part(refs[j + 1]).pads[0])\n",
        "\n",
        "# Array to store via information\n",
        "# Used to connect with xiao\n",
        "viaCol = [0] * 4\n",
        "\n",
        "# The diodes leading to COL1-3 in the switch matrix are aligned in a straight line, so they can be wired in the same way\n",
        "# Put a via across on the way\n",
        "for i in range(1, 4):\n",
        "  d1 = board.get_part(f\"D{i}\")\n",
        "  d2 = board.get_part(f\"D{i+3}\")\n",
        "  d3 = board.get_part(f\"D{i+7}\")\n",
        "  # Diode-Via is routed first to store via information\n",
        "  via = (\n",
        "    d1.pads[1]\n",
        "    .set_layer(LAYER_BOTTOM)\n",
        "    .w(\"l 180 f 1.25 r 45 f 2 l 45 f 6.5 l 45\")\n",
        "    .align(d2.pads[1], \"y\")\n",
        "    .wire()\n",
        "    .via()\n",
        "  )\n",
        "  viaCol[i] = via\n",
        "  # Route from via to pad of next diode\n",
        "  via.set_layer(LAYER_BOTTOM).meet(d2.pads[1])\n",
        "  d2.pads[1].set_layer(LAYER_BOTTOM).w(\n",
        "    \"l 180 f 1 r 45 f 2 l 45 f 6.5 l 45\"\n",
        "  ).align_meet(d3.pads[1], \"y\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "RHUfHju2lnf4"
      },
      "source": [
        "### Preview"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 965
        },
        "id": "TdpxBEHelnf9",
        "outputId": "4505fad8-49ab-4c5d-b885-1b856bfea16c"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "4sNQELnWwx5h"
      },
      "source": [
        "## Wiring: via to xiao (COL)\n",
        "Route from the stored via to xiao."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "uQ1xlQk8wx5m",
        "outputId": "66bcf5b0-6c5b-4774-fff5-11b4a61d82e8"
      },
      "outputs": [],
      "source": [
        "viaCol[1].set_layer(LAYER_TOP).w(\"l 45 f 11 l 45\").align_meet(\n",
        "  xiao_pads(\"COL1\"), \"x\"\n",
        ")\n",
        "viaCol[2].set_layer(LAYER_TOP).w(\"f 2 l 45 f 29.5 l 45\").align_meet(\n",
        "  xiao_pads(\"COL2\"), \"x\"\n",
        ")\n",
        "viaCol[3].set_layer(LAYER_TOP).w(\"f 4 l 45 f 48.5 l 45\").align_meet(\n",
        "  xiao_pads(\"COL3\"), \"x\"\n",
        ")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "BZp2rf73xjPt"
      },
      "source": [
        "### Preview"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 965
        },
        "id": "-eCOnReyxjPt",
        "outputId": "ab969902-942c-4924-c529-4064fd769043"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "ViFc_c0oxjPo"
      },
      "source": [
        "## Wiring: xiao to OLED\n",
        "Route from xiao to OLED.\n",
        "\n",
        "Enumerate the name of Net and look up the number of the pad connected from skidl's Net information."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "pEkSdb7YxjPs"
      },
      "outputs": [],
      "source": [
        "oledRef = \"DISP1\"\n",
        "\n",
        "for net in [\"GND\", \"3.3V\", \"SCL\", \"SDA\"]:\n",
        "  oledPin = board.get_part(oledRef).pads[get_pin_number_from_net(net, oledRef)]\n",
        "  xiao_pads(net).set_layer(LAYER_TOP).left(135).align_meet(oledPin, \"y\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "Xa0VwbDJwx5m"
      },
      "source": [
        "## Routing is completed\n",
        "Now all the routing is done!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 965
        },
        "id": "_z-12LbDwx5m",
        "outputId": "31258366-12f5-4a81-fa95-ff16facfab80"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "KduW_1h4OLdU"
      },
      "source": [
        "## Place pictures on PCB\n",
        "Place a logo and other images on the silk layer of the PCB.\n",
        "The image data to be used is in the resource at the beginning."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Ygvq-EqzOxDH"
      },
      "outputs": [],
      "source": [
        "DIR_IMGS = \"keyboard-made-by-python/\"\n",
        "\n",
        "board.add_bitmap(\n",
        "  (47.5, 28.5), \n",
        "  DIR_IMGS + \"hardware/imgs/python-logo.png\",\n",
        "  side=\"top\",\n",
        "  scale=0.85\n",
        ")\n",
        "\n",
        "board.add_bitmap(\n",
        "  (11.25, 45),\n",
        "  DIR_IMGS + \"hardware/imgs/QR.png\",\n",
        "  side=\"bottom\",\n",
        "  scale=0.75,\n",
        ")\n",
        "\n",
        "board.add_bitmap(\n",
        "  (11, 28.5),\n",
        "  DIR_IMGS + \"hardware/imgs/logo-python-powered-w-logo.png\",\n",
        "  side=\"bottom\",\n",
        "  layer=\"GBS\",\n",
        "  scale=0.7,\n",
        ")\n",
        "board.add_bitmap(\n",
        "  (11, 28.5),\n",
        "  DIR_IMGS + \"hardware/imgs/logo-python-powered-w-logoBG.png\",\n",
        "  side=\"bottom\",\n",
        "  layer=\"GBL\",\n",
        "  scale=0.7,\n",
        ")\n",
        "board.add_bitmap(\n",
        "  (11, 28.5),\n",
        "  DIR_IMGS + \"hardware/imgs/logo-python-powered-w-text.png\",\n",
        "  side=\"bottom\",\n",
        "  scale=0.7,\n",
        ")\n",
        "\n",
        "board.add_bitmap(\n",
        "  (47.5, 22),\n",
        "  DIR_IMGS + \"hardware/imgs/dm9-logo.png\",\n",
        "  side=\"bottom\",\n",
        "  scale=0.8,\n",
        ")\n",
        "\n",
        "board.add_bitmap(\n",
        "  (66.5, 22),\n",
        "  DIR_IMGS + \"hardware/imgs/hsgw-logo.png\",\n",
        "  side=\"bottom\",\n",
        "  scale=0.4,\n",
        ")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "32qY1ujHQRuA"
      },
      "source": [
        "### Preview"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "x6OEVJKUQO1W",
        "outputId": "01b64bbd-db89-4d8a-f7b6-c1349efc29d4"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "HQ_stk2iQmiy"
      },
      "source": [
        "## Place text on PCB\n",
        "Place texts on the silk layer of the PCB.\n",
        "\n",
        "Specify the position where the order number will be placed because I will order to JLCPCB."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1Lf-MAn1Qmiy"
      },
      "outputs": [],
      "source": [
        "board.add_text(\n",
        "  (47.5, 41.5),\n",
        "  \"https://github.com/hsgw/keyboard_made_by_python\",\n",
        "  scale=1.25,\n",
        "  side=\"bottom\",\n",
        ")\n",
        "\n",
        "board.add_text(\n",
        "  (BOARD_WIDTH - 39, 5),\n",
        "  \"KEYBOARD MADE BY PYTHON\",\n",
        "  scale=2,\n",
        "  side=\"bottom\",\n",
        "  justify=\"left\",\n",
        ")\n",
        "board.add_text(\n",
        "  (BOARD_WIDTH - 44, 4.75),\n",
        "  \"Rev.1\",\n",
        "  scale=1.25,\n",
        "  side=\"bottom\",\n",
        "  justify=\"left\",\n",
        ")\n",
        "board.add_text(\n",
        "  (BOARD_WIDTH - 47.5, 2.25),\n",
        "  \"(c) 2022, Takuya Urakawa / Dm9Records / 5z6p.com\",\n",
        "  scale=1.25,\n",
        "  side=\"bottom\",\n",
        "  justify=\"left\",\n",
        ")\n",
        "\n",
        "# MAGIC WORD for JLCPCB\n",
        "board.add_text(\n",
        "  (11.5, 55),\n",
        "  \"JLCJLCJLCJLC\",\n",
        "  scale=1.1,\n",
        "  side=\"bottom\",\n",
        ")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "3FdBYSKrQmiz"
      },
      "source": [
        "### Preview"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        },
        "id": "8lkcYwWVQmiz",
        "outputId": "3c3bb898-f533-4105-f32b-46901d75f7fc"
      },
      "outputs": [],
      "source": [
        "from IPython.display import Image\n",
        "\n",
        "board.save_png(\"kbd_python\", subdir=\"pcb_png\")\n",
        "\n",
        "Image(\"pcb_png/kbd_python_preview_all.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_top.png\")\n",
        "# Image(\"pcb_png/kbd_python_preview_bot.png\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "dvHvS6UHRLil"
      },
      "source": [
        "# Complete PCB and generate gerber files\n",
        "Finally, export the Gerber file for manufacturing and complete PCB."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "KIt69DLaRIgA",
        "outputId": "6d7c135a-49d8-4036-d2b3-84f697309b49"
      },
      "outputs": [],
      "source": [
        "board.save_gerbers(\"kbd_python\", subdir=\"pcb_gerber\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "TjFdKPM9Sont"
      },
      "source": [
        "Use kicad's Gerber Viewer to review Gerber files.\n",
        "\n",
        "![kicad's gerber viewer](../imgs/kicad_gerberviewer.png)\n",
        "\n",
        "It is also possible to convert from kicad's gerber viewer to pcbnew. For ordering, I converted to pcbnew, modified the silk and generated the gerber file again.\n",
        "\n",
        "![PCB converted to pcbnew](../imgs/kicad_gerber_to_pcbnew.png)"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Order PCB\n",
        "\n",
        "! [PCB received](. /imgs/pcb.jpg)\n",
        "\n",
        "I ordered a batch of Gerber files to JLCPCB, about $8 for 5 boards, and they arrived in less than 2 weeks.\n",
        "\n",
        "# Soldering the components\n",
        "\n",
        "![soldered PCB](../imgs/soldered_pcb_top.jpg)\n",
        "![soldered PCB](../imgs/soldered_pcb_bottom.jpg)\n",
        "\n",
        "I soldered the components. The PCB design is now complete."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "include_colab_link": true,
      "provenance": []
    },
    "kernelspec": {
      "display_name": ".venv",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.8.10 (default, Jun 22 2022, 20:18:18) \n[GCC 9.4.0]"
    },
    "vscode": {
      "interpreter": {
        "hash": "b68c839e49670d3aa77ea8bdd6f541fdcce81585b85d6e1d97674ad5f32db92f"
      }
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
